/*******************************************************************************
 * Copyright (c) 2001, 2002 Boris Pruessmann and others. All rights reserved.
 * This program and the accompanying materials are made available under the
 * terms of the Common Public License v0.5 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors: Boris Pruessmann - Initial implementation.
 ******************************************************************************/
package net.sourceforge.perforce.ui;

import net.sourceforge.perforce.core.resources.LocalFile;
import net.sourceforge.perforce.core.resources.LocalResource;
import net.sourceforge.perforce.core.resources.RemoteFolder;
import net.sourceforge.perforce.core.resources.RemoteResource;
import net.sourceforge.perforce.core.syncinfo.ResourceSyncInfo;
import net.sourceforge.perforce.ui.internal.Policy;

import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareEditorInput;
import org.eclipse.compare.CompareUI;
import org.eclipse.compare.ITypedElement;
import org.eclipse.compare.ResourceNode;
import org.eclipse.compare.internal.BufferedResourceNode;
import org.eclipse.compare.structuremergeviewer.DiffNode;
import org.eclipse.compare.structuremergeviewer.DiffTreeViewer;
import org.eclipse.compare.structuremergeviewer.Differencer;
import org.eclipse.compare.structuremergeviewer.IDiffContainer;
import org.eclipse.compare.structuremergeviewer.IDiffElement;
import org.eclipse.compare.structuremergeviewer.IStructureComparator;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;

/**
 * @version $Revision: 488 $
 * @author <a href="mailto:bpruessmann@users.sourceforge.net">Boris
 *         Pruessmann</a>
 */
public class PerforceCompareEditorInput extends CompareEditorInput {
  // ---------------------------------------------------------------------------
  // Private Constants

  /** Indicates that two nodes are equal. */
  private static final int NODE_EQUAL = 0;
  /** Indicates that two nodes are not equal. */
  private static final int NODE_NOT_EQUAL = 1;
  /** Indicates that it's unknown whether two nodes are equal or not. */
  private static final int NODE_UNKNOWN = 2;

  // ---------------------------------------------------------------------------
  // Private Variables

  /** Stores reference to the element displayed on the left side of the viewer. */
  private ITypedElement left;
  /** Stores reference to the element displayed on the right side of the viewer. */
  private ITypedElement right;
  /** Stores reference to the element displayed on the ancestor pane. */
  private ITypedElement ancestor;
  /** Stores the diff */
  private Object root;
  /** Stores the diff tree viewer. */
  private DiffTreeViewer diffViewer;

  // -------------------------------------------------------------------------
  // Public Constructors

  /**
   * Constructor for PerforceCompareEditorInput.
   * 
   * @param left element displayed on the left.
   * @param right element displayed on the right.
   */
  public PerforceCompareEditorInput(ITypedElement left, ITypedElement right) {
    this(left, right, null);
  }

  /**
   * Constructor for PerforceCompareEditorInput.
   * 
   * @param left element displayed on the left.
   * @param right element displayed on the right.
   * @param ancestor element displayed in ancestor pane.
   */
  public PerforceCompareEditorInput(ITypedElement left, ITypedElement right, ITypedElement ancestor) {
    super(new CompareConfiguration());

    this.left = left;
    this.right = right;
    this.ancestor = ancestor;
  }

  //----------------------------------------------------------------------------
  // -- Public Methods

  @Override
  public Viewer createDiffViewer(Composite parent) {
    Viewer v = super.createDiffViewer(parent);
    if (v instanceof DiffTreeViewer) {
      diffViewer = (DiffTreeViewer) v;
    }

    return v;
  }

  @Override
  public void saveChanges(IProgressMonitor pm) throws CoreException {
    super.saveChanges(pm);
    if (root instanceof DiffNode) {
      try {
        commit(pm, (DiffNode) root);
      } finally {
        if (diffViewer != null) {
          diffViewer.refresh();
        }
        setDirty(false);
      }
    }
  }

  // ---------------------------------------------------------------------------
  // Protected Methods

  /**
   * Sets the diff viewer.
   * 
   * @param theDiffViewer the diff viewer instance.
   */
  protected void setDiffViewer(DiffTreeViewer theDiffViewer) {
    diffViewer = theDiffViewer;
  }

  @Override
  protected Object prepareInput(IProgressMonitor monitor) throws InterruptedException {
    boolean threeWay = ancestor != null;
    if (right == null || left == null) {
      return null;
    }

    initLabels();

    Differencer differencer = new Differencer() {
      @Override
      protected boolean contentsEqual(Object input1, Object input2) {
        int compare = teamEqual(input1, input2);
        if (compare == NODE_EQUAL) {
          return true;
        } else if (compare == NODE_NOT_EQUAL) {
          return false;
        }

        return super.contentsEqual(input1, input2);
      }

      @Override
      protected void updateProgress(IProgressMonitor progressMonitor, Object node) {
        super.updateProgress(progressMonitor, node);
        progressMonitor.worked(1);
      }

      @Override
      protected Object[] getChildren(Object input) {
        if (input instanceof IStructureComparator) {
          Object[] children = ((IStructureComparator) input).getChildren();
          if (children != null) {
            return children;
          }
        }
        return null;
      }

      @Override
      protected Object visit(Object data, int result, Object ancestor, Object left, Object right) {
        return new InternalDiffNode2((IDiffContainer) data, result, (ITypedElement) ancestor,
            (ITypedElement) left, (ITypedElement) right);
      }
    };

    try {
      monitor.beginTask(Policy.bind("PerforceCompareEditorInput.comparing"), 30);

      IProgressMonitor sub = new SubProgressMonitor(monitor, 10);
      try {
        sub.beginTask(Policy.bind("PerforceCompareEditorInput.comparing"), 100);
        root = differencer.findDifferences(threeWay, sub, null, ancestor, left, right);

        if (root != null) {
          ResourceNode node = (ResourceNode) left;
          IResource resource = node.getResource();

          DiffNode result = (DiffNode) root;
          if (resource instanceof IContainer) {
            IDiffElement[] children = result.getChildren();
            result = new InternalDiffNode((IContainer) resource, result.getKind());
            for (int i = 0; i < children.length; i++) {
              result.add(children[i]);
            }
          }

          IContainer parent = null;
          while ((parent = resource.getParent()) != null) {
            DiffNode tempResult = new InternalDiffNode(parent, result.getKind());
            tempResult.add(result);

            result = tempResult;
            resource = parent;
          }

          root = result;
        }

        return root;
      } finally {
        sub.done();
      }
    } catch (OperationCanceledException e) {
      throw new InterruptedException(e.getMessage());
    } finally {
      monitor.done();
    }
  }

  /**
   * Compares two nodes to determine if they are equal. Returns NODE_EQUAL of
   * they are the same, NODE_NOT_EQUAL if they are different, and NODE_UNKNOWN
   * if comparison was not possible.
   * 
   * @param left the left object.
   * @param right the right object.
   * @return NODE_EQUAL, NODE_NOT_EQUAL or NODE_UNKNOWN
   */
  protected int teamEqual(Object left, Object right) {
    boolean threeWay = ancestor != null;

    ResourceNode localFileNode = null;
    if (threeWay) {
      if (right instanceof ResourceNode) {
        localFileNode = (ResourceNode) right;
      }
    } else {
      if (left instanceof ResourceNode) {
        localFileNode = (ResourceNode) left;
      }
    }

    if (localFileNode != null) {
      if (localFileNode.getResource().getType() == IResource.FILE) {
        LocalFile localFile =
            (LocalFile) LocalResource.createFromResource(localFileNode.getResource());

        if (!localFile.isCheckedOut() && !localFile.isDirty() && !localFile.isOutOfDate()) {
          return NODE_EQUAL;
        }
      }
    }

    if (left instanceof PerforceResourceEditionNode && right instanceof PerforceResourceEditionNode) {
      RemoteResource leftResource = ((PerforceResourceEditionNode) left).getRemoteResource();
      RemoteResource rightResource = ((PerforceResourceEditionNode) right).getRemoteResource();

      ResourceSyncInfo leftSyncInfo = leftResource.getSyncInfo(null);
      ResourceSyncInfo rightSyncInfo = rightResource.getSyncInfo(null);

      if (leftSyncInfo != null && rightSyncInfo != null) {
        if (leftSyncInfo.getHaveRev() == rightSyncInfo.getHaveRev()) {
          return NODE_EQUAL;
        }
      } else if (leftSyncInfo == null
          && (rightSyncInfo != null && rightSyncInfo.getHeadAction().equals(
              ResourceSyncInfo.ACTION_DELETE))) {
        return NODE_EQUAL;
      } else {
        return NODE_UNKNOWN;
      }
    }

    return NODE_UNKNOWN;
  }

  //----------------------------------------------------------------------------
  // - Private Methods

  /**
   * Recursively walks the diff tree and commits all changes.
   * 
   * @param pm the progress monitor
   * @param node the diff node
   * 
   * @throws CoreException in case of an error.
   */
  private static void commit(IProgressMonitor pm, DiffNode node) throws CoreException {
    ITypedElement left = node.getLeft();
    if (left instanceof BufferedResourceNode) {
      ((BufferedResourceNode) left).commit(pm);
    }

    IDiffElement[] children = node.getChildren();
    if (children != null) {
      for (int i = 0; i < children.length; i++) {
        IDiffElement element = children[i];
        if (element instanceof DiffNode) {
          commit(pm, (DiffNode) element);
        }
      }
    }
  }

  /**
   * Sets up the title and pane labels for the comparison view.
   */
  private void initLabels() {
    CompareConfiguration cc = getCompareConfiguration();

    String leftLabel = getLabel(left);
    cc.setLeftLabel(leftLabel);
    cc.setLeftImage(left.getImage());

    String rightLabel = getLabel(right);
    cc.setRightLabel(rightLabel);
    cc.setRightImage(right.getImage());

    String title = null;
    if (ancestor != null) {
      cc.setAncestorLabel(getLabel(ancestor));
      cc.setAncestorImage(ancestor.getImage());

      Object[] arguments =
          new Object[] {guessResourceName(), getVersionLabel(ancestor), getVersionLabel(left),
              getVersionLabel(right)};

      title = Policy.bind("PerforceCompareEditorInput.titleAncestor", arguments);
    } else {
      String leftName = null;
      if (left != null) {
        leftName = left.getName();
      }

      String rightName = null;
      if (right != null) {
        rightName = right.getName();
      }

      if (leftName != null && !leftName.equals(rightName)) {
        Object[] arguments =
            new Object[] {leftName, getVersionLabel(left), rightName, getVersionLabel(right)};

        title = Policy.bind("PerforceCompareEditorInput.titleNoAncestorDifferent", arguments);
      } else {
        Object[] arguments =
            new Object[] {guessResourceName(), getVersionLabel(left), getVersionLabel(right)};

        title = Policy.bind("PerforceCompareEditorInput.titleNoAncestor", arguments);
      }
    }

    setTitle(title);
  }

  /**
   * Returns the label for the given input element.
   * 
   * @param element the input element.
   * @return the label.
   */
  private String getLabel(ITypedElement element) {
    return element.getName();
  }

  /**
   * Returns a guess of the resource name being compared, for display in the
   * title.
   * 
   * @return the guessed resourcename.
   */
  private String guessResourceName() {
    if (left != null) {
      return left.getName();
    } else if (right != null) {
      return right.getName();
    } else if (ancestor != null) {
      return ancestor.getName();
    }

    return "";
  }

  /**
   * Returns the label for the given input element.
   * 
   * @param element the iput element
   * @return the version label.
   */
  private String getVersionLabel(ITypedElement element) {
    if (element instanceof ResourceNode) {
      return Policy.bind("PerforceCompareEditorInput.workspaceLabel");
    } else if (element instanceof PerforceResourceEditionNode) {
      RemoteResource edition = ((PerforceResourceEditionNode) element).getRemoteResource();
      if (edition instanceof RemoteFolder) {
        return ((RemoteFolder) edition).getRevision();
      } else {
        return "#" + edition.getSyncInfo(null).getHeadRev();
      }
    }

    return element.getName();
  }

  //----------------------------------------------------------------------------
  // -- Nested Classes

  /** Internal Diff Node. */
  static class InternalDiffNode extends DiffNode {
    // -----------------------------------------------------------------------
    // Private Variables

    /** Stores the container. */
    private IContainer container;

    // ---------------------------------------------------------------------
    // Public Constructors

    /**
     * Constructor for InternalDiffNode.
     * 
     * @param theContainer associated container.
     * @param kind kind value.
     */
    public InternalDiffNode(IContainer theContainer, int kind) {
      super(kind);
      container = theContainer;
    }

    //--------------------------------------------------------------------------
    // Public Methods

    @Override
    public String getName() {
      return container.getName();
    }

    @Override
    public Image getImage() {
      return CompareUI.getImage(container);
    }
  }

  /** Internal Diff Node */
  static class InternalDiffNode2 extends DiffNode implements IAdaptable {
    // ---------------------------------------------------------------------
    // Public Constructors

    /**
     * Constructor for InternalDiffNode2.
     * 
     * @see DiffNode#DiffNode(IDiffContainer, int, ITypedElement, ITypedElement,
     *      ITypedElement)
     */
    public InternalDiffNode2(IDiffContainer parent, int kind, ITypedElement ancestor,
        ITypedElement left, ITypedElement right) {
      super(parent, kind, ancestor, left, right);
    }

    //--------------------------------------------------------------------------
    // Public Methods

    @SuppressWarnings("unchecked")
    // Class
    public Object getAdapter(Class adapter) {
      if (adapter == IResource.class) {
        ResourceNode resourceNode = (ResourceNode) getLeft();
        return resourceNode.getResource();
      }

      return null;
    }
  }
}
